home *** CD-ROM | disk | FTP | other *** search
/ Windows News 2010 Summer - Disc 1 / WN_Ete2010_CD1.iso / Onglet5 / Weezo / Weezo setup.exe / {code_appDir} / www / ext / audio.php < prev    next >
PHP Script  |  2010-05-21  |  29KB  |  739 lines

  1. <?php
  2. /**
  3.  * Send a requested AUDIO FILE or PLAYLIST to user from a redirected /ext/audio request
  4.  * this script is used for file access from embeded or external audio players
  5.  *
  6.  * 3 kind of audio file can be processed :
  7.  *    - audio files (.mp3, .wav...)
  8.  *    - existing playlist (.m3u files)
  9.  *    - PHP playlist (used by audio explorer scripts), named *playlist*.m3u[.xml]
  10.  *   - single mp3 playlist (used by audio explorer scripts),named *singleMP3*.mp3.xml, used to bypass flash player limitation (cannot read somme characters like "&"...)
  11.  * if a .xml extension is added on a m3u or a mp3 file, the actual output is converted to an XML playlist file designed for being red by flash player.
  12.  *
  13.  * All (mime) video files can be processed. Only simple "streaming" is supported (no playlist or XML stuff...)
  14.  *
  15.  * URI must be formated like "/external/sess_$id/[audio/$bitrate/]urlencoded($filename)
  16.  * where "$id" is an active session id, and "urlencoded(filename)" is url-encoded requested
  17.  * file name (must include absolute path, and not relative path)
  18.  *
  19.  * If audio/$bitrate/ is specified, output file is reencoded with FFMPEG encoder
  20.  *
  21.  * If $filename is an existing m3u file, an modified m3u is sent to user, replacing song's filenames by
  22.  * reformated filenames (/external/sess_$id/...)
  23.  *
  24.  * If $fileName is *playlist*.m3u or *playlist*.m3u.xml,
  25.  *
  26.  * PHP version 5
  27.  *
  28.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  29.  * that is available through the world-wide-web at the following URI:
  30.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  31.  * the PHP License and are unable to obtain it through the web, please
  32.  * send a note to license@php.net so we can mail you a copy immediately.
  33.  *
  34.  * @category   NA
  35.  * @package    NA
  36.  * @author     Nicolas Bruley / Peer 2 World <contact@weezo.net>
  37.  * @copyright  2005-2009 Nicolas Bruley / Peer 2 World
  38.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  39.  * @version    CVS: $Id:$
  40.  * @link       http://www.weezo.net
  41.  * @since      File available since Release 1.0.0
  42.  */
  43. define('SESSION_NAME_PREFIX','sess_');
  44. define('ASF_HEADER_LENGTH',444);
  45.  
  46. $resampledAudioFile=false;
  47. $bitrate=false;
  48.  
  49. /**
  50.  * @desc Reencode to WMA and stream to output
  51.  *
  52.  * @param string $completeFilename
  53.  * @param integer $bitrate
  54.  */
  55. function reencodeWMA($completeFilename, $bitrate=64){
  56.     /**
  57.      * Replace file size and number of packets in ASF header
  58.      *
  59.      * @param string $data
  60.      */
  61.     function processASFHeader($data,$totalLength=-1){
  62.         static $buffer='';
  63.         static $bufferProcessed=0;
  64.  
  65.  
  66.         if($totalLength==-1) $_ENV['outputLength']+=strlen($data); else $_ENV['outputLength']=$totalLength;
  67.         cfDebugUpdateBuffer($_ENV['outputLength']);
  68.         if($bufferProcessed) {
  69.             if($totalLength>=$_ENV['wmaFileSize']) {
  70.                 echo substr($data,0,strlen($data)-($totalLength-$_ENV['wmaFileSize']));
  71.                 cfDebugUpdateBuffer('coupe');
  72.                 return true;
  73.             }
  74.             else echo $data;
  75.             //cfDebugUpdateBuffer(strlen($data).' -> '.number_format($_ENV['outputLength'],null,'.',' '));
  76.             return;
  77.             // Limit sent data to announced file size
  78.         }
  79.         else{
  80.             $buffer.=$data;
  81.             // Process buffer
  82.             if(strlen($buffer)>=ASF_HEADER_LENGTH){
  83.                 cfDebugUpdateBuffer('Process ASF header');
  84.                 $buffer=$_ENV['baseHeader'].substr($buffer,ASF_HEADER_LENGTH);
  85.  
  86.                 // Replace number of packets
  87.                 $buffer[434]=$buffer[86]=$_ENV['wmaPackets'][0];
  88.                 $buffer[435]=$buffer[87]=$_ENV['wmaPackets'][1];
  89.  
  90.                 // Replace file size
  91.                 $pfs=pack('V',$_ENV['wmaFileSize']);
  92.                 $buffer[70]=$pfs[0];
  93.                 $buffer[71]=$pfs[1];
  94.                 $buffer[72]=$pfs[2];
  95.                 $buffer[73]=$pfs[3];
  96.                 $pfs=pack('V',$_ENV['wmaFileSize']-394);
  97.                 $buffer[410]=$pfs[0];
  98.                 $buffer[411]=$pfs[1];
  99.                 $buffer[412]=$pfs[2];
  100.                 $buffer[413]=$pfs[3];
  101.  
  102.                 // Replace stuff by other stuff...
  103. //                            $buffer[118]=chr(2);
  104. //                             Streaming genome ?
  105.                 $buffer[94]=ord(96);
  106.                 $buffer[95]=ord(66);
  107.                 $buffer[96]=ord(41);
  108.                 $buffer[97]=ord(102);
  109. /*
  110.                 $buffer[102]=ord(64);
  111.                 $buffer[103]=ord(9);
  112.                 $buffer[104]=ord(58);
  113.                 $buffer[105]=ord(109);
  114. */
  115.                 echo $buffer;
  116.                 $bufferProcessed=true;
  117.                 $buffer='';
  118.             }
  119.         }
  120.     }
  121.  
  122.     /**
  123.      * @dest estimate WMA reencoded file size and number of packets
  124.      *
  125.      * @param string $cfn: source file
  126.      * @param integer $br: output bitrate (32,64,128)
  127.      * @return array (filesize, number of packets)
  128.      */
  129.     function estimatedReencodedSize($cfn,$br){
  130.         static $ratio=array('32'=>4700,'64'=>8700,'128'=>18000);
  131.  
  132.         // Try to get file length
  133.         $ai=efGetAudioInfo($cfn,false,array('vbrParsing'=>1));
  134.         if(!isset($ai['length'])) return array(false,false);
  135.  
  136.         // Transform ms to s
  137.         $length=$ai['length']/1000;
  138.  
  139.         if(!isset($ratio[$br])) return array(false,false);
  140.         //return (array(1818044,floor((1818044-444)/3200)));
  141.         return (array(ASF_HEADER_LENGTH+floor($ratio[$br]*$length),floor($ratio[$br]*$length/3200)));
  142.     }
  143.  
  144.     cfPingUpdate();
  145.     header('Content-Type: audio/x-ms-wma'); // any audio format reencoded to wma
  146.     header('Pragma: features="broadcast"');
  147.     header('Pragma: timeout=3000');
  148.     //if(!strpos($_SERVER['HTTP_USER_AGENT'],'pera')) header('Content-Type: audio/x-ms-wma'); else header('Content-Type: text/html');
  149.  
  150.     // Parse received Pragma
  151.     $inHeaders=getallheaders();
  152.     if(isset($inHeaders['Pragma'])){
  153.         $inPragma=cfExplode2Levels(',','=',$inHeaders['Pragma']);
  154.         cfDebugUpdateBuffer($inHeaders['Pragma']);
  155.     }
  156.  
  157.     // Set audio quality
  158.     if(!$bitrate || $bitrate=='original') $bitrate=64;
  159.     if($bitrate==128) $audioArgs='-ab 128000 -ac 1';
  160.     elseif ($bitrate==64) $audioArgs='-ab 64000 -ac 1';
  161.     elseif ($bitrate==32) $audioArgs='-ab 32000 -ac 1';
  162.     else $audioArgs='';
  163.  
  164.     // Generate base ASF header
  165.     $cmdLine='"'.cfFfmpegExe().'" -y -i "'.$completeFilename.'" -t 2 -acodec wmav2 '.$audioArgs.' -f asf -';
  166.     $s2=cfStreamProc($cmdLine,array('stdout'=>'return'));
  167.     $_ENV['baseHeader']=substr($s2,0,ASF_HEADER_LENGTH);
  168.  
  169.     $cmdLine=str_replace('-t 2 ','',$cmdLine);
  170.     cfDebugUpdateBuffer('Cmd line: '.$cmdLine);
  171.  
  172.     // Client only want ASF header
  173.     if(isset($inHeaders['max-duration']) && $inHeaders['max-duration']=='0'){
  174.         header('Content-Length: '.strlen($_ENV['baseHeader']));
  175.         cfDebugUpdateBuffer('+++++++++++++++++++');
  176.         die($_ENV['baseHeader']);
  177.     }
  178.  
  179.     // Estimate reencoded file size and number of WMA packets
  180.     //$ai=efGetAudioInfo($completeFilename,false,array('vbrParsing'=>1));
  181.     list($estimatedFileSize, $estimatedBlocks)=estimatedReencodedSize($completeFilename,$bitrate);
  182.     //cfDbg($estimatedFileSize.'<>'.((strlen($s2)-ASF_HEADER_LENGTH))*$ai['length']/2000);
  183.  
  184.  
  185.     $_ENV['wmaPackets']=pack('v',$estimatedBlocks);
  186.     $_ENV['wmaFileSize']=$estimatedFileSize;
  187.     $_ENV['outputLength']=0;
  188.     header('Content-Length: '.$estimatedFileSize);
  189.     cfDebugUpdateBuffer('est file size : '.$estimatedFileSize);
  190.  
  191.     // Stream file
  192.     //cfDbg($cmdLine);
  193.  
  194.     cfStreamProc($cmdLine,array('stdout'=>'processASFHeader'));
  195.     if($_ENV['outputLength']<$estimatedFileSize && connection_aborted()) {
  196.         echo str_pad('',$estimatedFileSize-$_ENV['outputLength'],chr(0));
  197.         cfDebugUpdateBuffer('PAD with '.($estimatedFileSize-$_ENV['outputLength']).' NULL chars');
  198.     }
  199.     if(connection_aborted()) cfDebugUpdateBuffer('Connection aborted'); else cfDebugUpdateBuffer('All streamed');
  200.     exit;
  201. }
  202.  
  203. /**
  204.  * @desc Return a converted m3u or xml formated string from an existing m3u file
  205.  *  - from m3u file :
  206.  *        File names of original m3u file are replaced by extAccess formated URL
  207.  *
  208.  *        if $xmlOutput is true, output is XML formated so it can be red by flash audio player
  209.  *        else output is formated like a normal m3u file.
  210.  *
  211.  * @param string $inFilename : complete file name of m3u file
  212.  * @param string $bitrate : max output bitrate
  213.  * @param boolean $xmlOutput : true if output is XML formated, false if m3u formated
  214.  * @return string XML File
  215.  */
  216. function convertM3U($m3uFilename, $bitrate, $xmlOutput){
  217.     // Playlist header
  218.     if($xmlOutput) $output='<?xml version="1.0" encoding="utf-8"?>'."\n".'<playlist volume="'.((cfUGetVar('audioVolume')!==false)?cfUGetVar('audioVolume'):'80').'">'."\n"; else $output="#EXTM3U\n";
  219.  
  220.     $nb=0; // number of songs in playlist
  221.  
  222.     // Internal playlist (explorer/music resource) or temporary playlist (musicDB, play artist or album)
  223.     if($m3uFilename=='*playlist*.m3u' || $m3uFilename=='*tmp*playlist*.m3u'){
  224.         // If filename = *playlist*.m3u, generates a m3u file pointing on cfRGetVar('playlist') or cfRGetVar('playlistShuffled') array of songs
  225.         if($m3uFilename=='*tmp*playlist*.m3u') $playlist=cfRGetVar('playlistTmp'); // Temp (artist or album) playlist
  226.         else if(cfRGetVar('shufflePlaylist')) $playlist=cfRGetVar('playlistShuffled'); // Shuffled playlist
  227.         else $playlist=cfRGetVar('playlist'); // Regular playlist
  228.  
  229.         foreach ($playlist as $song){
  230.             // external ref (returned by flash player to browser every time a new song is played)
  231.             if(!isset($song['externalRef'])) $song['externalRef']= base64_encode(cfResourceRelativePath($song['completeFileName']));
  232.             // resampling
  233.             if($bitrate) $externalFileName=cfExtAudio(($song['completeFileName']), "audio/".$bitrate."/", 'absolute');
  234.             else $externalFileName=cfExtAudio(($song['completeFileName']),'','absolute');
  235.             // Create XML node
  236.             if($xmlOutput) $output.='<audio externalRef="'.$song['externalRef'].'" songLabel="'.cfXMLEncodeProperty(cfUTF8Encode($song['label'],false,false)).'" file="'.$externalFileName."\" />\n";
  237.             // Or M3U lines
  238.             else $output.= '#EXTINF:'.$song['label']."\n".$externalFileName."\n";
  239.             $nb++;
  240.         }
  241.         cfLog('extAccess.php: audio playlist generated from internal playlist. '.$nb.' records generated',LOG_DBG);
  242.     }
  243.     // Single mp3 file
  244.     elseif($m3uFilename=='*singleMP3*.m3u'){
  245.         // resampling
  246.         if($bitrate) $externalFileName=cfExtAudio(cfRGetVar('singleMP3'), "audio/".$bitrate."/", 'absolute');
  247.         else $externalFileName=cfExtAudio(cfRGetVar('singleMP3'),'','absolute');
  248.         // label
  249.         if(cfRGetVar('singleMP3Label')) $label=cfRGetVar('singleMP3Label');
  250.         else $label=cfUTF8Encode(cfFileWithoutExtension(basename(cfRGetVar('singleMP3'))));
  251.  
  252.         if($xmlOutput) $output.='<audio songLabel="'.cfXMLEncodeProperty($label).'" file="'.$externalFileName."\" />\n";
  253.         else $output.= '#EXTINF:'.cfUTF8Decode($label)."\n".$externalFileName."\n";
  254.  
  255.         cfLog('extAccess.php: single mp3 playlist generated',LOG_DBG);
  256.     }
  257.     // else (existing m3u file)
  258.     else{
  259.         if(($handle = fopen ($m3uFilename, "r"))){
  260.             $label=false;
  261.             while (!feof ($handle)) {
  262.                 $m3uFile = trim(fgets($handle, 4096));
  263.                 if(substr($m3uFile,0,8)=="#EXTINF:"){
  264.                     $label=substr($m3uFile,8);
  265.                     $label=substr($label,strpos($label,',')+1);
  266.                 }
  267.                 elseif(substr($m3uFile,0,1)!="#"){
  268.                     $completeFilename=cfDirName($m3uFilename).'/'.str_replace('\\','/',$m3uFile);
  269.                     if(file_exists($completeFilename) && efFileType($completeFilename)=='audio'){
  270.                     if($bitrate) $externalFileName=cfExtAudio($completeFilename, 'audio/'.$bitrate.'/',(($xmlOutput)?'relative':'relative'));
  271.                     else $externalFileName=cfExtAudio(($completeFilename),'',(($xmlOutput)?'relative':'relative'));
  272.                     $externalRef=base64_encode(cfResourceRelativePath($completeFilename));
  273.                     if($xmlOutput)
  274.                         $output.='<audio '.(($label)?'songLabel="'.cfXMLEncodeProperty(cfUTF8Encode($label,false,false)).'" ':'').'file="'.$externalFileName.'"  externalRef="'.$externalRef.'"'."/>\n";
  275.                     else
  276.                         $output.= '#EXTINF:'.$label."\n".cfGGetVar('hostName').$externalFileName."\n";
  277.                     $label=false;
  278.                     $nb++;
  279.                     }
  280.                 }
  281.             }
  282.             fclose ($handle);
  283.         }
  284.     cfLog('extAccess.php: audio playlist generated from existing playlist. '.$nb.' records added',LOG_DBG);
  285.     }
  286.     if($xmlOutput) $output.='</playlist>';
  287.  
  288.     cfDebugUpdateBuffer($output,0);
  289.     return $output;
  290. }
  291.  
  292. /**
  293.  * @desc Check published audio access rights and send xml playlist and flash reencoded files to output
  294.  *
  295.  * @param array $uri: parsed URI ("previewImg"=>1 to generate a snapshot)
  296.  */
  297. function audioPublished($uri){
  298.     require_once(INCLUDE_DIR.'explorerFunctions.php');
  299.  
  300.     if(cfMGetVar('debugAsync')) cfDebugSetBuffer('audio',array("TYPE"=>"published audio")+$uri);
  301.  
  302.     $id=$uri['dlToken'];
  303.     $tokenList=efTokensRead();
  304.  
  305.     // If link is not published audio or streamed direct link, exit
  306.     if(!(@$token=$tokenList[$id])) cfLog('invalid published video id ('.$id.')',LOG_DBG);
  307.     if($token['type']!=='publishAudio' && ($token['type']!=='directLink' || @$token['streaming']!=='audio')) cfLog('invalid published audio id ('.$id.')',LOG_DBG);
  308.  
  309.     if(!isset($uri['file'])) die();
  310.  
  311.     $files=array();
  312.  
  313.     // Generate playlist from directory content
  314.     if(is_dir($token['filename'])){
  315.         foreach (cfGlob($token['filename'].'/*.*') as $cfn){
  316.             if(efFileType($cfn)=='audio') $files[]=array('cfn'=>$cfn);
  317.         }
  318.     }
  319.     // Generate playlist from list of files
  320.     else{
  321.         foreach (explode('|',$token['filename']) as $cfn){
  322.             if(file_exists($cfn)) $files[]=array('cfn'=>$cfn);
  323.         }
  324.     }
  325.  
  326.     /**
  327.      * Playlist
  328.      */
  329.     if($uri['file']=='playlist.xml'){
  330.         // Increase number of downloads and save (except for directLinks for which downloads are counted on actual audio file download
  331.         // in order not to have a negative offset of 1 on limited number of downloads)
  332.         if($token['type']!=='directLink') {
  333.             $tokenList[$id]['limitNb']--;
  334.             efTokensWrite($tokenList);
  335.         }
  336.  
  337.         // Get songs id3 info
  338.         foreach ($files as $k=>$v) {
  339.             $files[$k]['ai']=efGetAudioInfo($files[$k]['cfn']);
  340.             $files[$k]['uri']=str_replace('playlist.xml',$k.'.mp3',$_SERVER['REQUEST_URI']);
  341.         }
  342.  
  343.         header('Content-type: text/xml');
  344.         require_once(INCLUDE_DIR.'fileInfoFunctions.php');
  345.         $xmlPlaylist=fiArrayToPlaylist($files,'xml','URI');
  346.         cfDebugUpdateBuffer($xmlPlaylist);
  347.         die($xmlPlaylist);
  348.     }
  349.     /**
  350.      * Streamed file
  351.      */
  352.     else{
  353.         // Increase number of viewed times
  354.         if($token['type']=='directLink') {
  355.             $tokenList[$id]['limitNb']--;
  356.             efTokensWrite($tokenList);
  357.         }
  358.     }
  359.  
  360.     /**
  361.      * Reencoded (or not) audio files
  362.      */
  363.     $k=cfFileWithoutExtension($uri['file']);
  364.     if(!isset($files[$k])) die();
  365.     $cfn=$files[$k]['cfn'];
  366.  
  367.     cfDebugUpdateBuffer('Play file: '.$cfn);
  368.  
  369.     $bitrate=@$token['maxOutputBitRate'];
  370.  
  371.     header('Content-type: audio/mpeg');
  372.  
  373.     // Reencode to mp3 and send to ouptut
  374.     if($bitrate!='original' || cfFileExtension($cfn)!='mp3'){
  375.         // Audio quality
  376.         if(!$bitrate || $bitrate=='original') $audioArgs='';
  377.         elseif ($bitrate==32) $audioArgs='-ar 11025 -ab '.$bitrate.' -ac 1';
  378.         else $audioArgs='-ar 44100 -ab '.$bitrate.' -ac 1';
  379.  
  380.         // ID3 tagging (not supported yet)
  381.         // ?
  382.  
  383.         // ffmpeg command line
  384.         $cmdLine='"'.cfFfmpegExe().'" -i "'.$completeFilename.'" '.$audioArgs.' -f mp3 -map_meta_data : -';
  385.  
  386.         // Reencode
  387.         cfStreamProc($cmdLine,array('stdout'=>'stdout'),array('speedLimit'=>0));
  388.     }
  389.     // Send raw mp3 file to output
  390.     else {
  391.         header('Content-length: '.filesize($cfn));
  392.         echo file_get_contents($cfn);
  393.     }
  394.     exit;
  395. }
  396.  
  397.  
  398. require(INCLUDE_DIR.'explorerFunctions.php');
  399. require(INCLUDE_DIR.'mime_type.php');
  400.  
  401. //Get URI and verify is starts with "/audio/"
  402. $uri=$_SERVER['REQUEST_URI'];
  403. //cfDbg($uri);
  404.  
  405. $outputCodec='mp3';
  406.  
  407. // Published playlist
  408. $parsedURI=cfParseExtURI();
  409. if(isset($parsedURI['dlToken'])) audioPublished($parsedURI);
  410.  
  411. // Verify that URI starts by '/audio/'
  412. if(substr($uri,0,7)!='/audio/') cfHTTPError(404);
  413. $uri=stripcslashes(rawurldecode(substr($uri,7)));
  414. cfLog('/ext/audio.php: file requested : '.$uri,LOG_DBG);
  415.  
  416. // verify that passed session ID exists, without starting session
  417. $passedsessionId=substr($uri,0,strpos($uri,"/")); $found=false;
  418. $uri=substr($uri,strlen($passedsessionId)+1);
  419. if(!($handle = opendir(wSession_save_path()))) cfHTTPError(403);
  420. while ((($file = @readdir($handle))!==false) && !($found)) {if(is_file(wSession_save_path()."/".$file) && $passedsessionId==$file)     $found=true;}
  421. closedir($handle);
  422.  
  423. // If passed session not found, display 404 and exit
  424. if(!$found) cfHTTPError(404);
  425.  
  426.  
  427.  
  428. // START SESSION
  429. wSession_id(substr($passedsessionId,strlen(SESSION_NAME_PREFIX)));
  430. wSession_start();
  431.  
  432. // Set active resource id
  433. if(substr($uri,0,5)!='resId') cfHTTPError(404);
  434. $resId=substr($uri,5,strpos($uri,'/')-5);
  435. if(!is_numeric($resId) || !isset($_SESSION['res'][$resId]) || !is_array($_SESSION['res'][$resId]))  cfHTTPError(403);
  436. $_SESSION['activeResourceId']=$resId;
  437. $uri=substr($uri,strpos($uri,'/')+1);
  438.  
  439.  
  440.  
  441. // verifie that client's IP match with session's IP
  442. if($_SERVER['REMOTE_ADDR']!=$_SESSION['accountIP'] && cfGGetVar('disableClientSessionIPControl')!='true' && !isset($_SERVER['HTTPS'])) {
  443.     cfLog('extAccess.php: passed session id does not match with IP => 404',LOG_ER);
  444.     cfHTTPError(403);
  445. }
  446.  
  447. // unencode filename
  448. if(cfUTF8Encode(cfUTF8Decode($uri,true,false,false,2048),false,true)!=$uri) // if encoding-decoding doesn't work then string is already utf8-encoded
  449.     $completeFilename=$uri;
  450. else
  451.     $completeFilename=cfUTF8Decode($uri,true,true,false,2048);
  452.  
  453.  
  454. // DEBUG
  455. if(cfMGetVar('debugAsync')) cfDebugSetBuffer('audio',array('CFN'=>$completeFilename));
  456.  
  457. $completeFilename=str_replace('*weezoSharp*','#',$completeFilename);
  458. $completeFilename=str_replace('*resourceBasePath*',cfRGetVar('path'),$completeFilename);
  459. $completeFilename=str_replace('*resourceDataDirBasePath*',cfAppResourceDir(),$completeFilename);
  460.  
  461. // If a resampled audio file is asked for, get bitrate and file name
  462. if(substr($completeFilename,0,6)=='audio/'){
  463.       $resampledAudioFile=true;
  464.     $completeFilename=substr($completeFilename,6);
  465.     $bitrate=substr($completeFilename,0,strpos($completeFilename,"/"));
  466.     $completeFilename=substr($completeFilename,strpos($completeFilename,"/")+1);
  467.  
  468.     // Reencoding for WMP mobile
  469.     if(substr($completeFilename,-strlen(WMP_MOBILE_AUDIO_EXT))==WMP_MOBILE_AUDIO_EXT){
  470.         $completeFilename=substr($completeFilename,0,-strlen(WMP_MOBILE_AUDIO_EXT));
  471.         $outputCodec='wmpMobile';
  472.     }
  473.     else $outputCodec='mp3';
  474.  
  475.     // No resampling if original bitrate on mp3 !
  476.     if($bitrate=='original' && cfFileExtension($completeFilename)=='mp3') $resampledAudioFile=false;
  477.     cfLog('extAccess.php: resampled audio file requested. Bitrate='.$bitrate,LOG_DBG);
  478. }
  479.  
  480. // Verify that requested bitrate is not superior to resource's max allowed bitrate
  481. if(!is_numeric($bitrate)) $bitrate='original';
  482. if(($maxBitrate=cfRGetVar('maxOutputBitRate')) && $maxBitrate!='original'){
  483.     if($bitrate=='original' || $bitrate>$maxBitrate) {
  484.         $resampledAudioFile=true; $bitrate=$maxBitrate;
  485.         if(cfBGetVar('wmp') && !cfBGetVar('flash')) $outputCodec='wmpMobile'; else $outputCodec='mp3';
  486.     }
  487. }
  488.  
  489. // Remove rnd part for playlists, singleMp3 and singleVideo
  490. if(substr($completeFilename,-14)=='*playlist*.m3u' || substr($completeFilename,-18)=='*playlist*.m3u.xml' || substr($completeFilename,-15)=='*singleMP3*.m3u' || substr($completeFilename,-19)=='*singleMP3*.m3u.xml' || substr($completeFilename,-17)=='*singleVideo*.xml') {
  491.     //$completeFilename=substr($completeFilename,strrpos($completeFilename,'*',-10));
  492.     $completeFilename=substr($completeFilename,strpos($completeFilename,'*'));
  493. }
  494.  
  495.  
  496. // If file extension is ".m3u.xml", file is a playlist that need to be converted into XML format requested by FLASH audio player
  497. if(cfFileExtension($completeFilename)=='xml'){
  498.     $completeFilename=cfFileWithoutExtension($completeFilename);
  499.     $xmlOutput=true;
  500. }
  501. else $xmlOutput=false;
  502.  
  503. // If file is a m3u file, convert it's content to URL links
  504. if(cfFileExtension($completeFilename)=='m3u'){
  505.     $convertedM3UFile=convertM3U($completeFilename, $bitrate, $xmlOutput); // generate playlist
  506.     if($completeFilename=='*tmp*playlist*.m3u') $completeFilename='tmpplaylist.m3u';
  507.     if($completeFilename=='*playlist*.m3u') $completeFilename='playlist.m3u';
  508.     if($completeFilename=='*singleMP3*.m3u') $completeFilename='singleMP3.m3u';
  509.     // restore xml file extension
  510.     if($xmlOutput) $completeFilename.='.xml';
  511. }
  512. else{
  513.     // Resource specific file source and rights function : bypass common file's existence and access rights
  514.     // and (may) replace given file name by real file name
  515.     if(cfRGetVar('extAccessFunction')) {
  516.         $extAccessFunction = create_function('$completeFilename', cfRGetVar('extAccessFunction'));
  517.         if(!($completeFilename=$extAccessFunction($completeFilename))) cfHTTPError(404);
  518.     }
  519.     // Check file's existence and access rights
  520.     else{
  521.         // If file doesn't exist, Display 404 and exit
  522.         if(!file_exists($completeFilename))    cfHTTPError(404);
  523.  
  524.         // If file download not allowed, Display 404 and exit
  525.         if(!cfFileRights($completeFilename,'download')) cfHTTPError(403);
  526.  
  527.         // If file is not an audio or video file, Display 404 and exit
  528.         if($mt=mimeType(cfFileExtension($completeFilename))) $type=substr($mt,0,strpos($mt,"/")); else $type=false;
  529.         if($type!='audio' && $type!='video') cfHTTPError(403);
  530.     }
  531. }
  532.  
  533. // Check if a file is non-mp3 with flash player
  534. if(!$resampledAudioFile && cfFileExtension($completeFilename)!='m3u' && cfFileExtension($completeFilename)!='mp3' && cfRGetVar('audioPlayer')=='flash'){
  535.     $resampledAudioFile=true;
  536.     $bitrate='original';
  537.     if(cfBGetVar('wmpMobile') && !cfBGetVar('flash')) $outputCodec='wmpMobile'; else $outputCodec='mp3';
  538. }
  539.  
  540. // Caching
  541. if(substr($completeFilename,0,10)=='*playlist*' || substr($completeFilename,0,11)=='*singleMP3*' || substr($completeFilename,0,13)=='*singleVideo*') $cacheFile=false; else $cacheFile=true;
  542. if(cfBGetVar('name')=='Wii') $cacheFile=false;
  543.  
  544. // Check if a range request can be satified
  545. $acceptRange=(($resampledAudioFile || cfFileExtension($completeFilename)=='m3u' || cfFileExtension($completeFilename)=='xml')?false:true);
  546. if(cfBGetVar('name')=='iPhone') $acceptRange=true; // Force accept range to true on iPhone
  547.  
  548. // Check if client did a range request
  549. $clientHeaders=getallheaders();
  550. $range=((isset($clientHeaders['Range']) && strtolower(substr($clientHeaders['Range'],0,6)=='bytes='))?trim(substr($clientHeaders['Range'],6)):$range=false);
  551.  
  552. /***********************************************************************************************************************************
  553.  * Range request
  554.  **********************************************************************************************************************************/
  555. if($range && strpos($range,'-')!==false && $range!='0-' && !$resampledAudioFile/* iPhone resampled content: 416 is sent by cfRangeAccessibleOutputSend function */){
  556.     // Reject range requests on resampled files or m3u or xml
  557.     if(!$acceptRange){
  558.         header('HTTP/1.1 416 Requested Range Not Satisfiable');
  559.         $range=false;
  560.     }
  561.     else{
  562.         $range=trim($range);
  563.         $fileSize=cfFileSize($completeFilename);
  564.  
  565.         // From
  566.         $from=substr($range,0,strpos($range,'-'));
  567.         if(!strlen($from)) $from=0;
  568.         if(!is_numeric($from) || $from>$fileSize) $rangeErr=true;
  569.  
  570.         // To
  571.         $rto=substr($range,strpos($range,'-')+1);
  572.         if(!strlen($rto)) {
  573.             $to = $fileSize-1;
  574.         }
  575.         else $to=$rto;
  576.         if($to+1>$fileSize || $to<$from) $rangeErr=true;
  577.         $content_size = 1 + $to - $from;
  578.  
  579.         cfDebugUpdateBuffer('Range requested: '.$from.'-'.$to);
  580.  
  581.         // Incorrect range
  582.         if(isset($rangeErr)){
  583.             header('HTTP/1.1 416 Requested Range Not Satisfiable');
  584.             $range=false;
  585.         }
  586.         else{
  587.             header('HTTP/1.1 206 Partial Content');
  588.             header("Content-Range: bytes $from-$to/$fileSize");
  589.             $contentLength=$content_size;
  590.             $range=array('from'=>$from,'to'=>1+$to);
  591.         }
  592.  
  593.         // iPhone optimization (unused)
  594.         if($from==0 && $to==1 && $resampledAudioFile && $outputCodec=='mp3') {
  595.             cfDebugUpdateBuffer('Send "ID"');
  596.             die('ID');
  597.         }
  598.     }
  599. }
  600. else $range=false;
  601.  
  602. // Set content-length if possible
  603. if(!$range){
  604.     if(cfFileExtension($completeFilename)=='m3u' || cfFileExtension($completeFilename)=='xml') $contentLength=strlen($convertedM3UFile);
  605.     elseif (!$resampledAudioFile) $contentLength=cfFileSize($completeFilename);
  606. }
  607.  
  608.  
  609. /***********************************************************************************************************************************
  610.  * HEADERS
  611.  **********************************************************************************************************************************/
  612.  
  613. // Mime type
  614. if ($resampledAudioFile && cfFileExtension($completeFilename)!='m3u' && cfFileExtension($completeFilename)!='xml') {
  615.     if($outputCodec=='mp3') header('Content-Type: audio/mpeg'); // any audio format reencoded to mp3
  616. }
  617. else header('Content-Type: '.mimeType($completeFilename)); // m3u or xml playlist or other audio format
  618.  
  619. // Cache
  620. if($cacheFile){
  621.     header("Pragma:"); // Do not remove, required by IE in SSL mode
  622.     header('Cache-Control: private');
  623.     header('ETag: "'.md5($completeFilename).'"');
  624.     header('Last-Modified: '.gmdate("D, d M Y H:i:s",@filemtime($completeFilename)).' GMT');
  625.     header('Expires: '.gmdate("D, d M Y H:i:s",time()+3600*24*100).' GMT');
  626. }
  627. // No cache
  628. else{
  629.     if(cfGetBrowser()=='ie') header("Pragma:"); // Do not remove, required by IE in SSL mode
  630.     else header("Pragma: no-cache");
  631.     header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
  632.     header("Expires: Thu, 01 Jan 1970 01:00:00 GMT");
  633. }
  634.  
  635. // Other
  636. header('X-Powered-By:');
  637. header('Content-Description: file transfert');
  638. if(cfBGetVar('name')!=='iPhone') header('Content-Disposition: inline; filename="'.cfUTF8Encode(basename($completeFilename),false,false).(($outputCodec=='wmpMobile')?WMP_MOBILE_AUDIO_EXT:'').'"'); // iPhone doesn't like this header...
  639. if($acceptRange) header("Accept-Ranges: bytes"); else header("Accept-Ranges: none");
  640.  
  641. // Content-length
  642. if(isset($contentLength)) header('Content-Length:'.$contentLength);
  643. elseif (cfBGetVar('name')=='Android') header('Content-Length:'.filesize($completeFilename)); // Android and iPhone require a content length
  644.  
  645. cfLog('/ext/audio.php: file download initiated:'.$completeFilename,LOG_DBG);
  646.  
  647.  
  648.  
  649.  
  650. /*
  651.  * SEND FILE TO OUTPUT
  652.  */
  653.  
  654. // XML or M3U playlist : send to output
  655. if(cfFileExtension($completeFilename)=='m3u' || cfFileExtension($completeFilename)=='xml'){
  656.     echo $convertedM3UFile;
  657. }
  658. else{
  659.     // Close session so file download don't block other scripts from same user
  660.     // Note : no possible log beyond this point
  661.     wSession_write_close();
  662.  
  663.     // Log (history) access to file
  664.     cfLogContentAccess($completeFilename);
  665.  
  666.     // Free some memory
  667.     unset($_SESSION);
  668.  
  669.     // Set download speed limit
  670.     $speedLimit=cfUGetVar('downloadSpeedLimit');
  671.  
  672.     // Extend disconnection time for standalone media players
  673.     if(cfBGetVar('standaloneMediaPlayer')) cfPingUpdate('force',600);
  674.  
  675.     // Resampled / converted mp3
  676.     if($resampledAudioFile){
  677.         set_time_limit(0);
  678.         
  679.         // Audio quality
  680.         if(!$bitrate || $bitrate=='original') $audioArgs='-aq 0';
  681.         elseif($bitrate==32) $audioArgs='-ar 22050 -ab '.$bitrate.' -ac 1';
  682.         elseif($bitrate==64) $audioArgs='-ar 22050 -ab '.$bitrate.' -ac 2';
  683.         else $audioArgs='-ar 44100 -ab '.($bitrate/2).' -ac 2';
  684.  
  685.         // Stream mp3 resampled file
  686.         if($outputCodec=='mp3'){
  687.  
  688.             // ID3 tagging
  689.             $cmdLine='"'.cfFfmpegExe().'" -i "'.$completeFilename.'" '.$audioArgs.' -f mp3 -map_meta_data : -';
  690.  
  691.             // Debug
  692.             cfDebugUpdateBuffer('Start streaming reencoded mp3: '.$cmdLine);
  693.  
  694.             // Reencoding speed limit
  695.             $streamOptions=array('speedLimit'=>$speedLimit);
  696.  
  697.             // Reencoding range
  698.             if($range) $streamOptions+=array('range'=>$range);
  699.  
  700.             // iPhone special: pre-generate file and send as range request-compatible
  701.             if(cfBGetVar('name')=='iPhone'){
  702.                 /*
  703.                 if($range) header('HTTP/1.1 206 Partial Content');
  704.                 //header("Content-Length: 999999999");
  705.                 header("Accept-Ranges: bytes");
  706.                 if($range){
  707.                     list($from,$to)=explode('-',$range);
  708.                     header("Content-Range: bytes $range/999999999");
  709.                     header("Content-Length: ".(1+$to-$from));
  710.                 }
  711.                 */
  712.                 cfRangeAccessibleOutputStart();
  713.                 cfStreamProc($cmdLine,array('stdout'=>'stdout'));
  714.                 cfRangeAccessibleOutputSend();
  715.                 exit;
  716.             }
  717.  
  718.             // Reencode
  719.             cfStreamProc($cmdLine,array('stdout'=>'stdout'),$streamOptions,$streamedLength);
  720.  
  721.             // Debug
  722.             cfDebugUpdateBuffer('DONE STREAMING MP3');
  723.         }
  724.  
  725.         // Stream WMP mobile format resampled file
  726.         elseif ($outputCodec=='wmpMobile')    reencodeWMA($completeFilename, $bitrate);
  727.     }
  728.     // Stream raw file
  729.     else {
  730.         // Debug
  731.         cfDebugUpdateBuffer('Start streaming raw file, speedlimit:'.(int)$speedLimit);
  732.  
  733.         // Send file to output
  734.         cfStreamFile($completeFilename,false,$speedLimit,$range);
  735.  
  736.         cfDebugUpdateBuffer('Done');
  737.     }
  738. }
  739. ?>